﻿using System.Diagnostics.Contracts;
using System.Security.Cryptography;
using System.Text;

namespace Devonline.Core;

/// <summary>
/// byte[] 类相关扩展方法
/// </summary>
public static class ByteExtensions
{
    /// <summary>
    /// 将字节数组去除末尾无效字符
    /// </summary>
    /// <param name="data">字节数组</param>
    /// <param name="trimChar">对其字符, 默认 \0 </param>
    /// <returns></returns>
    public static byte[] TrimEnd(this byte[] data, char trimChar = char.MinValue)
    {
        var index = 0;
        for (; index < data.Length; index++)
        {
            if (data[index] == trimChar)
            {
                break;
            }
        }

        return data[AppSettings.UNIT_ZERO..index];
    }

    /// <summary>
    /// 数字转换为16进制显示的字符
    /// </summary>
    /// <param name="i"></param>
    /// <returns></returns>
    public static char GetHexValue(this int i)
    {
        Contract.Assert(i >= 0 && i < 16, "i is out of range.");
        if (i < 10)
        {
            return (char)(i + '0');
        }

        return (char)(i - 10 + 'A');
    }
    /// <summary>
    /// Converts an array of bytes into a String.  
    /// </summary>
    /// <param name="value"></param>
    /// <param name="startIndex"></param>
    /// <param name="length"></param>
    /// <param name="spliter"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"></exception>
    /// <exception cref="ArgumentOutOfRangeException"></exception>
    /// <exception cref="ArgumentException"></exception>
    public static string ToString(this byte[] value, int startIndex, int length, char spliter = AppSettings.CHAR_SPACE)
    {
        if (startIndex < 0 || startIndex >= value.Length && startIndex > 0)
        {
            // Don't throw for a 0 length array.
            throw new ArgumentOutOfRangeException(nameof(startIndex), "start index out of range");
        }

        if (length < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(length), "generic positive out of range");
        }

        if (startIndex > value.Length - length)
        {
            throw new ArgumentException("arg array plus off too small");
        }

        Contract.EndContractBlock();

        if (length == 0)
        {
            return string.Empty;
        }

        var maxValue = int.MaxValue / 3;
        if (length > maxValue)
        {
            // (Int32.MaxValue / 3) == 715,827,882 Bytes == 699 MB
            throw new ArgumentOutOfRangeException(nameof(length), "argument out of range length too large:" + maxValue);
        }

        var chArrayLength = length * 3;
        var chArray = new char[chArrayLength];
        var index = startIndex;
        for (int i = 0; i < chArrayLength; i += 3)
        {
            var b = value[index++];
            chArray[i] = GetHexValue(b / 16);
            chArray[i + 1] = GetHexValue(b % 16);
            chArray[i + 2] = spliter;
        }

        // We don't need the last spliter character
        return new string(chArray, 0, chArray.Length - 1);
    }
    /// <summary>
    /// Converts an array of bytes into a String
    /// </summary>
    /// <param name="value"></param>
    /// <param name="spliter"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"></exception>
    public static string ToString(this byte[] value, char spliter = AppSettings.CHAR_SPACE)
    {
        ArgumentNullException.ThrowIfNull(value);
        Contract.Ensures(Contract.Result<string>() != null);
        Contract.EndContractBlock();
        return ToString(value, 0, value.Length, spliter);
    }
    /// <summary>
    /// Converts an array of bytes into a String
    /// </summary>
    /// <param name="value"></param>
    /// <param name="startIndex"></param>
    /// <param name="spliter"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"></exception>
    public static string ToString(this byte[] value, int startIndex, char spliter = AppSettings.CHAR_SPACE)
    {
        Contract.Ensures(Contract.Result<string>() != null);
        Contract.EndContractBlock();
        return ToString(value, startIndex, value.Length - startIndex, spliter);
    }

    /// <summary>
    /// 字节流转字符串数组
    /// </summary>
    /// <param name="data">原始字节流</param>
    /// <param name="spliter">分隔符, 默认分隔符: '-' </param>
    /// <returns></returns>
    public static string ToHexString(this byte[] data, char spliter = AppSettings.CHAR_HLINE)
    {
        var value = BitConverter.ToString(data);
        if (spliter == AppSettings.CHAR_HLINE)
        {
            return value;
        }

        return value.Replace(AppSettings.DEFAULT_CONNECTOR_STRING, (spliter == char.MinValue) ? string.Empty : spliter.ToString());
    }
    /// <summary>
    /// 字符串数组转字节流
    /// </summary>
    /// <param name="hexString">原始二进制字符串</param>
    /// <param name="spliter">分隔符</param>
    /// <param name="length">返回的总长度</param>
    /// <returns></returns>
    public static byte[]? ToBinary(this string hexString, char spliter = AppSettings.CHAR_SPACE, int? length = default)
    {
        if (spliter == char.MinValue)
        {
            var len = hexString.Length / AppSettings.UNIT_TWO;
            if (length.HasValue && len < length.Value)
            {
                len = length.Value;
            }

            var bytes = new byte[len];
            for (int i = 0; i < bytes.Length; i++)
            {
                bytes[i] = Convert.ToByte(hexString[(i * AppSettings.UNIT_TWO)..(i * AppSettings.UNIT_TWO + AppSettings.UNIT_TWO)], AppSettings.UNIT_SIXTEEN);
            }

            return bytes;
        }

        var binaryArray = hexString.Split(spliter);
        if (binaryArray.Length > 0)
        {
            var len = binaryArray.Length;
            if (length.HasValue && len < length.Value)
            {
                len = length.Value;
            }

            var bytes = new byte[len];
            for (int i = 0; i < binaryArray.Length; i++)
            {
                if (binaryArray[i].IsNotNullOrEmpty())
                {
                    bytes[i] = Convert.ToByte(binaryArray[i], AppSettings.UNIT_SIXTEEN);
                }
            }

            return bytes;
        }

        return null;
    }

    /// <summary>
    /// 将字节数组显示为ASCII格式的字符串，当遇到0x20以下及0x7E以上的不可见字符时，使用十六进制的数据显示
    /// Display the byte array as a string in ASCII format, when encountering invisible characters below 0x20 and above 0x7E, use hexadecimal data to display
    /// </summary>
    /// <param name="data">字节数组信息</param>
    /// <returns>ASCII格式的字符串信息</returns>
    public static string ToAsciiString(this byte[] data)
    {
        if (data.Length == 0)
        {
            return string.Empty;
        }

        var result = new StringBuilder();
        foreach (var b in data)
        {
            if (b is >= 0x20 and <= 0x7E)
            {
                result.Append((char)b);
            }
            else
            {
                result.Append($"\\{b:X2}");
            }
        }

        return result.ToString();
    }

    /// <summary>
    /// 从字节流按顺序读取信息组织成对象
    /// </summary>
    /// <typeparam name="T">要转换的对象类型</typeparam>
    /// <param name="data">原始字节流</param>
    /// <param name="encoding">编码方式</param>
    /// <param name="trim">是否过滤去掉多余空格</param>
    /// <returns></returns>
    public static T? GetTo<T>(this byte[] data, Encoding? encoding = default, bool trim = true) where T : class, new()
    {
        var fields = typeof(T).GetFieldAttributes();
        if (fields.Any())
        {
            fields = fields.OrderBy(x => x.Index).ToList();
            var t = new T();
            var index = 0;
            encoding ??= Encoding.UTF8;
            foreach (var field in fields)
            {
                if (data.Length > field.Size)
                {
                    var value = encoding.GetString(data, index, field.Size);
                    if (!string.IsNullOrWhiteSpace(value))
                    {
                        if (trim)
                        {
                            value = value.Trim();
                        }

                        field.Property.SetValue(t, value.To(field.Property.PropertyType.GetCoreType(), field.Format));
                    }

                    index += field.Size;
                }
            }

            return t;
        }

        return default;
    }

    /// <summary>
    /// 获取字节流的 Base64 编码值
    /// </summary>
    /// <param name="buffer"></param>
    /// <returns></returns>
    public static string GetBase64String(this byte[] buffer) => Convert.ToBase64String(buffer);

    /// <summary>
    /// 对字符串值按 THashAlgorithm 算法计算 Hash Code
    /// </summary>
    /// <typeparam name="THashAlgorithm">Hash 算法</typeparam>
    /// <param name="hashAlgorithm">hash 算法实例</param>
    /// <param name="buffer">待计算值</param>
    /// <returns></returns>
    public static byte[] GetHashValue<THashAlgorithm>(this THashAlgorithm hashAlgorithm, byte[] buffer) where THashAlgorithm : System.Security.Cryptography.HashAlgorithm => hashAlgorithm.ComputeHash(buffer);
    /// <summary>
    /// 获取字符串的 SHA256 Hash 值, 这是一种直接使用 SHA256 的更高效的方式
    /// </summary>
    /// <param name="buffer">原始字符串</param>
    /// <returns></returns>
    public static byte[] GetHashValue(this byte[] buffer) => SHA256.Create().ComputeHash(buffer);
    /// <summary>
    /// 获取字符串的 SHA256 Hash 值的 Base64 字符串表示形式, 这是一种直接使用 SHA256 的更高效的方式
    /// </summary>
    /// <param name="buffer">原始字符串</param>
    /// <returns></returns>
    public static string GetHashString(this byte[] buffer) => buffer.GetHashValue().GetBase64String();

    /// <summary>
    /// 计算数据的 CRC16 校验码, 默认低字节在前
    /// </summary>
    /// <param name="data">待校验数据</param>
    /// <returns></returns>
    public static byte[] CRC16(this byte[] data)
    {
        ushort crc = 0xffff;
        ushort polynom = 0xA001;

        for (int i = 0; i < data.Length; i++)
        {
            crc ^= data[i];
            for (int j = 0; j < 8; j++)
            {
                if ((crc & 0x01) == 0x01)
                {
                    crc >>= 1;
                    crc ^= polynom;
                }
                else
                {
                    crc >>= 1;
                }
            }
        }

        return BitConverter.GetBytes(crc);
    }
}